%% classes.erl
%%   Look up class definitions, generate class type-checking AST (abstract syntax tree) fragments.
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
%%
%% This module contains the entry point for the  parse transformation of ECT. 
%% It should be invoked via ect.hrl. (Function parse_transform/2.)
%%
-module(classes).
-export([is_class/2, field_accessor/5, lookup/2, fieldpos/3]).
-export([type_and_version_check/4, get_local_version/3, get_global_version/3, check_global_version/3]).

-include("common_defs.hrl").

%% Function: is_class(Classes, Name)
%% Purpose: determines if a class by a certain name is defined
%% Arguments:
%%   Classes::[#class{}] - list of class definitions
%%   Name::atom() - class name to look up
%% Returns: true if a class by Name is found amond Classes, false otherwise
is_class([#class{name = Name}|_], Name) ->
    true;
is_class([_|Classes], Name) ->
    is_class(Classes, Name);
is_class([], _) ->
    false.

%% Function: lookup(Classes, Name)
%% Purpose: finds a class definition by a certain class name in a list of class definitions
%% Arguments:
%%   Classes::[#class{}] - list of class definitions
%%   Name::atom() - name of class to look up
%% Returns: a #class{} record of the seeked class if found, notfound otherwise
lookup([Def = #class{name = Name}|_], Name) ->
    Def;
lookup([_|List], Name) ->
    lookup(List, Name);
lookup([], _) ->
    notfound.

%% Function: fieldpos(Definition, Field, CxClass)
%% Purpose: returns the position of a certain field in objects of a class
%% Arguments:
%%   Definition::#class{} - definition of the class
%%   Field::atom() - name of field
%%   CxClass::atom() - name of class from where the field is accessed, or none
%% Returns:
%%   {ok, GlobalPos, LocalPos} if Field exists in Definition, and it is accessible from CxClass
%%      it is accessible, if the field is public, or CxClass is the same class as Definition
%%   {error, ErrorMessageText} otherwise
fieldpos(#class{name = RecName, class_types = Types, fieldmap = Fields}, Field, CxClass) ->
    fieldpos(Fields, Field, RecName, Types, CxClass).

fieldpos([], Field, RecName, _, _) ->
    {error, io_lib:format("[ect] field ~p undefined in class ~p", [Field, RecName])};
fieldpos([#class_field{name = Field, access = Accessor, global_pos = GlobalPos, local_pos = LocalPos}|_], Field, RecName, Types, CxClass) ->
    case (Accessor =:= public orelse element(1, element(GlobalPos-1, Types)) =:= CxClass) of
    true ->
        {ok, GlobalPos, LocalPos};
    false ->
        {error, io_lib:format("[ect] field ~p of class ~p is not visible from here", [Field, RecName])}
    end;
fieldpos([_|Fields], Field, RecName, Types, CxClass) ->
    fieldpos(Fields, Field, RecName, Types, CxClass).

% -----------------------------

%% Function: field_accessor(Definition, FieldName, RecExpr, CxClass, Line)
%% Purpose: returns an AST fragment that can be used to access a field of an object
%% Arguments:
%%   Definition::#class{} - definition of the class
%%   FieldName::atom() - name of field
%%   RecExpr - an expression in AST form that is the object, whose field is accessed
%%   CxClass::atom() - name of class from where the field is accessed, or none
%%   Line::integer() - line number to use in AST nodes
%% Returns:
%%   {ok, AST} - where AST is the abstract syntax tree of the following expression:
%%               element(LocalPos, element(GlobalPos, RecExpr)
%%               (if the field exists, and can be accessed from CxClass)
%%   {error, ErrorMessageText} - if the field does not exist, or can not be accessed from CxClass
field_accessor(Definition, FieldName, RecExpr, CxClass, Line) ->
	case fieldpos(Definition, FieldName, CxClass) of
	{ok, GlobalPos, LocalPos} ->
		ast:element(LocalPos, ast:element(GlobalPos, RecExpr, Line), Line);
	{error, ErrMsg} ->
        throw({translateError, Line, ErrMsg})
	end.

%% Function: type_and_version_check(Definition, Version, Expr, Line)
%% Purpose: returns an AST fragment that is a guard which checks the type and version of an object
%% Arguments:
%%   Definition::#class{} - definition of the class
%%   Version::integer() - local version number to check
%%   Expr - an expression in AST form that is the object, whose field is accessed
%%   Line::integer() - line number to use in AST nodes
%% Returns:
%%   An abstract syntax tree fragment that checks type of Expr to be the class of Definition
%%   and checks its local version number corresponding the class of Definition to be Version.
type_and_version_check(Definition, Version, Expr, Line) ->
    {op, Line, 'andalso',
        ast:class_typecheck_guard(Definition, Expr, Line),
        {op, Line, '=:=', 
            {integer, Line, Version},
            get_local_version(Definition, Expr, Line)
        }
    }.

%% Function: type_and_version_check(Definition, Version, Expr, Line)
%% Purpose: creates an AST fragment that is a guard which checks the type and version of an object
%% Arguments:
%%   Definition::#class{} - definition of the class
%%   Version::integer() - local version number to check
%%   Expr - an expression in AST form that is the object, whose field is accessed
%%   Line::integer() - line number to use in AST nodes
%% Returns:
%%   An abstract syntax tree fragment that checks type of Expr to be the class of Definition
%%   and checks the local version number of Expr corresponding the class of Definition to be Version.
check_global_version(Definition, Expr, Line) ->
    {op, Line, '=:=',
        get_global_version(Definition, Expr, Line),
        {integer, Line, Definition#class.global_version}
    }.

%% Function: get_local_version(Definition, Expr, Line)
%% Purpose: creates an AST fragment that returns the local version of an object corresponding to a certain class
%% Arguments:
%%   Definition::#class{} - definition of the class, whose corresponding local version is seeked
%%   Expr - an expression in AST form that is the object, whose local version is seeked
%%   Line::integer() - line number to use in AST nodes
%% Returns:
%%   An abstract syntax tree fragment that returns the local version of Expr, that corresponds to the
%%   class of Definition.
get_local_version(#class{class_level = ClassLevel}, Expr, Line) ->
    ast:element(2, ast:element(ClassLevel+?FIELD_GLOBAL_STARTPOS-1, Expr, Line), Line).
get_global_version(#class{class_level = ClassLevel}, Expr, Line) ->
    ast:element(3, ast:element(ClassLevel+?FIELD_GLOBAL_STARTPOS-1, Expr, Line), Line).
